# coding=utf-8 
'''
Created on 2018年4月5日

@author: heguofeng
'''
import unittest
from pp_network import PPNetApp, FakeAppNet
from pp_tcp import Session, prepare_socket, Beater
import socket
import logging
from _thread import start_new_thread
from pp_link import PP_APPID, set_debug, NAT_TYPE
import struct
import time
import threading
import random
import select


'''define statics'''
SOCKS_VER5 = b'\x05'
METHOD_NO_AUTHENTICATION_REQUIRED = b'\x00'
METHOD_GSSAPI = b'\x01'
METHOD_USERNAME_PASSWORD = b'\x02'
METHOD_IANA_ASSIGNED_MIN = b'\x03'
METHOD_IANA_ASSIGNED_MAX = b'\x7F'
METHOD_RESERVED_FOR_PRIVATE_METHODS_MIN = b'\x80'
METHOD_RESERVED_FOR_PRIVATE_METHODS_MAX = b'\xFE'
METHOD_NO_ACCEPTABLE_METHODS = b'\xFF'

CMD_CONNECT = b'\x01'
CMD_BIND = b'\x02'
CMD_UDP = b'\x03'

RSV = b'\x00'
ATYP_IPV4 = 1
ATYP_DOMAINNAME = 3
ATYP_IPV6 = 4

REP_succeeded = b'\x00'
REP_general_SOCKS_server_failure = b'\x01'
REP_connection_not_allowed_by_ruleset = b'\x02'
REP_Network_unreachable = b'\x03'
REP_Host_unreachable = b'\x04'
REP_Connection_refused = b'\x05'
REP_TTL_expired = b'\x06'
REP_Command_not_supported = b'\x07'
REP_Address_type_not_supported = b'\x08'

class Sock5Client(object):
    '''
    listen on port
    send2Server(data) 
    
    '''
    def __init__(self,proxy_node,send2Peer,port=7070):
        self.port = port
        self.quitting = False
        self.proxy_node = proxy_node
        self.send2Peer = send2Peer
        self.sessions = {}   #{(node_id,session_id):[stage,socket,Session]}  stage 0 init 1 receiving 2 done
        self.session_id = 0
    
    def start(self):
        self.servsock = socket.socket()
        self.servsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.servsock.bind(("0.0.0.0",self.port))
        logging.info("sock5client: --> %s:%s --> %d -->" % ('0.0.0.0',self.port,self.proxy_node))        
        self.servsock.listen(1000)
        start_new_thread(self.listen, ())
        
    def quit(self):
        self.quitting = True
        self.servsock.close()
        
    def listen(self,):
        while not self.quitting:
            client_sock, client_addr = self.servsock.accept()
            start_new_thread(self.receive,(client_sock,))

    def get_session_id(self):
        self.session_id += 1
        return self.session_id             
            
    def receive(self,sock):
        session_id = self.get_session_id()
        self.sessions[(self.proxy_node,session_id)] = [1,sock,
               Session(send_process=lambda session,data: self.send2Peer(self.proxy_node,session,data),
                       receive_process=lambda session,data: self.send(self.proxy_node,session["id"],data))]
        start = 0
        end = 0
        sock.settimeout(60)
        while not self.quitting and self.sessions[(self.proxy_node,session_id)][0] == 1:
#         while not self.quitting:
            try:
                data = sock.recv(1024)
                if data:
                    logging.debug("session %d receive from local: %s"%(session_id,data[:20]))
                    end =  start+len(data)
                    session = {"id":session_id,"size":0,"start":start,"end":end}
                    self.sessions[(self.proxy_node,session_id)][2].send(session,data)
#                     self.send2Peer(self.proxy_node,session,data)
                    start = end 
            except socket.timeout:
                break
            except Exception as exp:
                logging.debug(exp.__str__())
                break
        session = {"id":session_id,"size":start,"start":start,"end":end}
        self.send2Peer(self.proxy_node,session,b"")
        sock.close()
#         session = {"id":session_id,"size":0,"start":0,"end":0}
        self.close(self.proxy_node,session)


    def send(self,node_id,session_id,data):
        logging.debug("send to %d:%d %s!"%(node_id,session_id,data[:20]))
        try:
            if (node_id,session_id) in self.sessions:
                self.sessions[(node_id,session_id)][1].sendall(data)
            else:
                logging.warning("not in sessions!")
        except OSError as exp:
            logging.debug(exp.__str__())
        pass   
    
    def close(self,node_id,session):
        session_id = session["id"]
        if (node_id,session_id) in self.sessions:
            del self.sessions[(node_id,session_id)]
            self.send2Peer(node_id,session,None)
            
    def process(self,node_id,session,data):
        session_id = session["id"]
        if (node_id,session_id) in self.sessions:
            stage = self.sessions[(node_id, session_id)][0]
            size,pos = self.sessions[(node_id, session_id)][2].receive(session,data)
#             if size == pos :
#                 self.sessions[(node_id, session_id)][0] = 2
        else:
            pass

class ProxyBase(object):        
    def __init__(self,port=7070):
        self.port = port
        self.quitting = False
        self.timer = None
        self.servsock = None
        self.count = 0  #session count
        self.sessions = {}  # {session_id:[client_sock,remote_sock,failurecount]
        self.session_id = 0

    def start(self):
        self.quitting = False
        self.count = 0
        self.servsock = prepare_socket(timeout=0,port=self.port)
        logging.info("proxy: --> %s:%d -->" % ('0.0.0.0',self.port))        
        self.servsock.listen(1000)
        start_new_thread(self.listen, ())
        start_new_thread(self.beat_null, ())

    def proxy_start(self):
        self.quitting = False
        self.count = 0
        self.servsock = prepare_socket(timeout=0,port=self.port)
        logging.info("proxy: --> %s:%d -->" % ('0.0.0.0',self.port))        
        self.servsock.listen(1000)
        start_new_thread(self.listen, ())
 

    def quit(self):
        self.quitting = True
        if self.timer:
            self.timer.cancel()
            self.timer = None
        if self.servsock:
            self.servsock.close()
            self.servsock = None            

    def listen(self,):
        while not self.quitting:
            try:
                client_sock, client_addr = self.servsock.accept()
            except Exception as exp:
                logging.warning(exp)
                pass
            else:
                start_new_thread(self.proxy_receive,(client_sock,client_addr))   

        
    def beat_null(self):
        '''
        just keep nat firewall know it is runing
        '''
        logging.debug("proxy server beat null")
        try:
            sock = prepare_socket(timeout=10,port=self.port)
            sock.connect((socket.inet_ntoa(struct.pack('I', socket.htonl(random.randint(1677721600, 1694498816)))),
                                random.randint(10000, 60000)))
        except Exception as exp:
#             logging.warning("error in beat null %s: %s "%(sock,exp.__str__()))
            pass
        
        for s_id in list(self.sessions.keys()):
            if not self.sessions[s_id][0] or  not self.sessions[s_id][1]:
                self.sessions[s_id][2] += 1
                if self.sessions[s_id][2] > 2:
                    self.sessions.pop(s_id)
            else:
                self.sessions.pop(s_id)
                
        if not self.quitting:
            self.timer = threading.Timer(60, self.beat_null)
            self.timer.start()        
        
#     def connectRemote(self,addr):
#         remote_socket = prepare_socket(timeout = 5)
#         try:
#             remote_socket.connect(addr)
#             remote_socket.settimeout(0)
#             return remote_socket
#         except socket.timeout:
#             pass
#         except Exception as exp:
#             logging.warning("connect %s:%d error %s"%(addr+(exp.__str__(),)))
#             return None        

    def connectRemote(self,addr):
        remote_socket = prepare_socket(timeout = 5)
        try:
            remote_socket.connect(addr)
            remote_socket.settimeout(0)
            return remote_socket
        except socket.timeout:
            pass
        except Exception as exp:
            logging.warning("connect %s:%d error %s"%(addr+(exp.__str__(),)))
            return None        
        
    def proxy(self,client_socket,remote_socket):
        self.count += 1
        while True:
            end = False
            try:
                socks = select.select([client_socket, remote_socket], [], [], 3)[0]
            except Exception as exp:
                logging.warning(exp.__str__())
                end = True
            else:
                for sock in socks:
                    try:
                        data = sock.recv(1024)
#                         logging.debug("receive data:"%data)
                    except Exception as exp:
                        logging.warning(exp.__str__())
                        end = True
                    else:
                        if not data:
                            continue
                        else:
                            try:
                                if sock is client_socket:
                                    logging.debug("%d bytes from client %s" % (len(data),data[:20]))
                                    remote_socket.sendall(data)
                                else:
                                    logging.debug( "%d bytes from server %s" % (len(data),data[:20]))
                                    client_socket.sendall(data)
                            except Exception as exp:
                                logging.warning(exp.__str__())
                                end = True
            if end:
                try:
                    self.count -= 1
                    client_socket.close()
                    remote_socket.close()
                except Exception as exp:
                    logging.warning(exp.__str__())
                    pass
                break
           
class ProxyClient(ProxyBase):
    '''
    use tcp mode
    listen on port
    send2Server(data) 
    
    '''
    def __init__(self,proxy_addr=None,port=7070,connectProxy=None):
        super().__init__(port)
        self.setProxy(proxy_addr)
        if connectProxy:
            self.connectProxy = connectProxy


    def setProxy(self,proxy_addr=None):
        self.proxy_addr = proxy_addr
        return self.proxy_addr
        
    def connectProxy(self,session_id):
        return super().connectRemote(self.proxy_addr)
        
#     def listen(self,):
#         while not self.quitting:
#             client_sock, client_addr = None,None
#             try:
#                 client_sock, client_addr = self.servsock.accept()
#             except Exception as exp:
#                 logging.warning(exp)
#                 pass
#             remote_socket = self.connectRemote(self.proxy_addr)
#             logging.debug("localsocket %s %s remote_socket %s"%(client_sock,self.proxy_addr,remote_socket))
#             if client_sock and remote_socket:
#                 start_new_thread(self.proxy,(client_sock,remote_socket))
#             else:
#                 if client_sock:
#                     client_sock.close()
#                 if remote_socket:
#                     remote_socket.close()
                    
    def proxy_receive(self,client_sock,client_addr):
        logging.debug("%s %s"%(client_sock,client_addr))
        if client_addr == self.proxy_addr:
            session_id = struct.unpack("I",client_sock.recv(4))[0]
            if session_id in self.sessions:
#                 self.sessions[session_id][1] = client_sock
                start_new_thread(self.proxy,(self.sessions[session_id][0],client_sock))    
        else:
            self.session_id += 1
            self.sessions[self.session_id] = [client_sock,None,0]
            remote_socket = self.connectProxy(self.session_id)
            if remote_socket:
#                 self.sessions[session_id][1] = remote_socket
                start_new_thread(self.proxy,(self.sessions[self.session_id][0],remote_socket))

                    
class ProxyServer(ProxyBase):    
    '''
    wait on port accept proxy client connect

    '''
    def __init__(self,send2Peer=None,port=7070):
        self.quitting = False
        self.send2Peer= send2Peer
        self.port = port
        self.sessions = {}   #{(node_id,session_id):[stage,socket,session]}
        self.timer = None
        self.count = 0
        

    def listen(self,):
        while not self.quitting:
            client_sock, client_addr = self.servsock.accept()
            start_new_thread(self.proxy_receive,(client_sock,client_addr))    
         

    def proxy_receive(self,client_sock,client_addr):
        try:
            data = client_sock.recv(7)
            if data:
                logging.debug(" proxy server receive data:(%d) %s "%(len(data),data[:20]))
#         except socket.timeout:
        except:
            return 
        if data[:7]==b"CONNECT":
            self.init_http(client_sock, data)
        else:
            self.init_sock5(client_sock, data)
        

    def init_sock5(self,client_sock,data):
        if len(data)>2:
            socks_version = data[0]
            if not socks_version == 5:
                logging.warning("discard error message %s"%data[:20])
                return
            method_number = data[1]
            methods_client_supported = data[2:2+method_number]
            logging.debug("client support method %s"%methods_client_supported)
            client_sock.sendall(SOCKS_VER5+METHOD_NO_AUTHENTICATION_REQUIRED)
            
            data = client_sock.recv(4)
            socks_version,command,rsv,address_type = data[:4]
            logging.debug("version %s command %s address_type %s"%(socks_version,command,address_type))
            address = data[3:4]
            if address_type == ATYP_DOMAINNAME:
                address += client_sock.recv(1)
                domain_length = address[1]
                address += client_sock.recv(domain_length+2)
                domain = address[2:2+domain_length]
                port = address[2+domain_length:]
                hostname = domain.decode()
            elif address_type == ATYP_IPV4:
                domain_length = 0
                address += client_sock.recv(6)
                domain = address[1:5].decode()
                hostname = '%s.%s.%s.%s' % (str(domain[0]), str(domain[1]), str(domain[2]), str(domain[3]))
                port = address[5:7]
            else:
                return False
            logging.debug("address %s hostname %s port %s"%(address,hostname,port))
            nport = struct.unpack(">H",port)[0]
                
            remote_socket = self.connectRemote(addr=(hostname, nport))
            if remote_socket:
                logging.info("connect remote %s:%d success. "%(hostname, nport))
                reply = SOCKS_VER5 + REP_succeeded +RSV + address
                client_sock.sendall(reply)
                start_new_thread(self.proxy, (client_sock,remote_socket))
            else:
                reply = SOCKS_VER5 + REP_Network_unreachable
                client_sock.sendall(reply)
        pass
                
    def init_http(self,client_sock,data):
        data1 = client_sock.recv(1024)
        data += data1
        logging.debug("receive data %s"%data[:20])
        parts = data.decode().split(" ")
        if parts[0]=="CONNECT":
            host_port = parts[1].split(":")
            logging.debug("receive connect to %s"%host_port)
        else:
            logging.warning(parts)
            return
            
        remote_socket = self.connectRemote(addr=(host_port[0], int(host_port[1])))
        if remote_socket:
            logging.info("connect remote %s:%d success. set stage 1 socket %s"%(host_port[0], int(host_port[1]),remote_socket) )
            client_sock.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")           
            start_new_thread(self.proxy, (client_sock,remote_socket))



class Sock5Server(object):    
    '''
    send2Client(peer_id,session_id,data)
    '''
    def __init__(self,send2Peer):
        self.quitting = False
        self.send2Peer= send2Peer
        self.sessions = {}   #{(node_id,session_id):[stage,socket,session]}
        
    def start(self):
        logging.info("sock5 server: --> client --> server  -->" )        
         
    def quit(self):
        self.quitting = True
        
    def process(self,node_id,session,data):
        session_id = session["id"]
        if (node_id,session_id) in self.sessions:
            stage = self.sessions[(node_id, session_id)][0]
            if stage==1:
                size,pos = self.sessions[(node_id, session_id)][2].receive(session,data)
                if size==pos:
                    self.sessions[(node_id, session_id)][0] = 2
#                 self.send(node_id, session_id,data)
            if stage == 0:
                self.init_sock5(node_id, session, data)
        else:
            if len(data)>=7 and data[:7]==b"CONNECT":
                self.init_http(node_id, session, data)
            else:
                self.init_sock5(node_id, session, data)

    def init_sock5(self,node_id,session,data):
        session_id = session["id"]
        if (node_id,session_id) in self.sessions:
            stage = self.sessions[(node_id, session_id)][0]
            if stage==1:
                self.self.sessions[(node_id, session_id)][2].receive(session,data)       
            if stage == 0 and len(data)>=9:
                socks_version,command,rsv,address_type = data[0],data[1],data[2],data[3]
                logging.debug("version %s command %s address_type %s"%(socks_version,command,address_type))
                index = 4
                if address_type == ATYP_DOMAINNAME:
                    domain_length = data[index]
                    domain = data[index+1:index+1+domain_length]
                    index += 1+domain_length
                    hostname = domain.decode()
                    port = data[index:index+2]
                    address = data[3:3+2+data[4]+2]
                elif address_type == ATYP_IPV4:
                    domain_length = 0
                    domain = data[index:index+4].decode()
                    hostname = '%s.%s.%s.%s' % (str(domain[0]), str(domain[1]), str(domain[2]), str(domain[3]))
                    port = data[index+4:index+6]
                    address = data[3:3+1+4+2]
                else:
                    return False
                logging.debug("address %s hostname %s port %s"%(address,hostname,port))
                nport = struct.unpack(">H",port)[0]
                
                remote_socket = self.connectRemote(addr=(hostname, nport))
                if remote_socket:
                    self.sessions[(node_id,session_id)] = [1,remote_socket,self.sessions[(node_id,session_id)][2]]
                    self.sessions[(node_id,session_id)][2].receive_pos += len(data)
                    logging.info("connect remote %s:%d success. set stage : %d"%(hostname, nport,
                                                                                 self.sessions[(node_id,session_id)][0]))
                    reply = SOCKS_VER5 + REP_succeeded +RSV + address
                    start_new_thread(self.receive, (node_id,session_id))
                else:
                    reply = SOCKS_VER5 + REP_Network_unreachable
                session =  {"id":session_id,"size":0,"start":0,"end":0}
                self.send2Peer(node_id,session,reply)
        elif len(data)>2:
            
            socks_version = data[0]
            if not socks_version == 5:
                logging.warning("discard error message %s"%data[:20])
                return
            method_number = data[1]
            methods_client_supported = data[2:2+method_number]
            logging.debug("client support method %s"%methods_client_supported)
            session =  {"id":session_id,"size":0,"start":0,"end":0}
            self.send2Peer(node_id,session,data=SOCKS_VER5+METHOD_NO_AUTHENTICATION_REQUIRED)
            self.sessions[(node_id,session_id)] = [0,None,
                            Session(send_process=lambda session,data: self.send2Peer(node_id,session,data),
                                    receive_process=lambda session,data: self.send(node_id,session["id"],data))]
            self.sessions[(node_id,session_id)][2].receive_pos = len(data)  
    
        pass
                
    def init_http(self,node_id,session,data):
        session_id = session["id"]
        if (node_id,session_id) in self.sessions:
            stage = self.sessions[(node_id, session_id)][0]
            logging.debug("stage : %d, data %s"%(stage,data[:20]))
            if stage==1:
                self.self.sessions[(node_id, session_id)][2].receive(session,data)   
            if stage == 0:
                pass
        else:
            parts = data.decode().split(" ")
            if parts[0]=="CONNECT":
                host_port = parts[1].split(":")
                logging.debug("receive connect to %s"%host_port)
            else:
                logging.warning(parts)
                return
            
            remote_socket = self.connectRemote(addr=(host_port[0], int(host_port[1])))
            if remote_socket:
                logging.info("connect remote %s:%d success. set stage 1 socket %s"%(host_port[0], int(host_port[1]),remote_socket) )           
                self.sessions[(node_id,session_id)]=[1,remote_socket,
                            Session(send_process=lambda session,data: self.send2Peer(node_id,session,data),
                                    receive_process=lambda session,data: self.send(node_id,session["id"],data))]  
                self.sessions[(node_id,session_id)][2].receive_pos = len(data)          
                start_new_thread(self.receive, (node_id,session_id))
                session =  {"id":session_id,"size":0,"start":0,"end":0}
                self.send2Peer(node_id,session,data=b"HTTP/1.1 200 Connection Established\r\n\r\n")

    def connectRemote(self,addr):
        server_sock = socket.socket()
        server_sock.settimeout(3)
        try:
            server_sock.connect(addr)
            return server_sock
        except socket.timeout:
            pass
        except Exception as exp:
            logging.warning("connect %s:%d error %s"%(addr+(exp.__str__(),)))
            return None
    
    def send(self,node_id,session_id,data):
        try:
            if (node_id,session_id) in self.sessions:
                self.sessions[(node_id,session_id)][1].sendall(data)
        except OSError as exp:
            logging.warning("send error %s"%exp.__str__())
        pass
        
    def receive(self,node_id,session_id):
        sock = self.sessions[(node_id,session_id)][1]
        sock.settimeout(60)
        start =  0
        end = 0
        while (node_id,session_id) in self.sessions:
            try:
                data = sock.recv(1024)
                if data:
                    logging.debug(" proxy server receive data: %s "%data[:20])
                    end = start + len(data)
                    session = {"id":session_id,"size":0,"start":start,"end":end}
                    start = end
                    self.sessions[(node_id,session_id)][2].send(session,data)                 
#                     self.send2Peer(node_id,session_id,data)
            except socket.timeout:
                break
            except Exception as exp:
                logging.warning("node %d:%s %s"%(node_id,session_id,exp.__str__()))
                break

        session = {"id":session_id,"size":start,"start":start,"end":end}
        self.send2Peer(node_id,session,b"")            
        sock.close()
#         session = {"id":session_id,"size":0,"start":0,"end":0}
#         self.close(node_id, session)

    def close(self,node_id,session):
        '''
        if session_id == 0 delete all restart client
        '''
        logging.debug("close by client %s"%session)
        session_id = session["id"]
        if (node_id,session_id) in self.sessions:
            del self.sessions[(node_id,session_id)]
        if session_id == 0:
            for key in list(self.sessions.keys()):
                if key[0]==node_id:
                    del self.sessions[key]
            
class PPSock5(PPNetApp):
    '''
    Sock5Client
        listen on port,send to server 
    Sock5Server
        listen client connect,forward to outside 
    config should have proxy_node = 100 
    '''
    class Sock5Message(PPNetApp.AppMessage):
        def __init__(self,**kwargs):
            tags_id={"send":1,"send_ack":2,"data":3,"close":4,"reget":5}
            tags_string={1:"send",2:"send_ack",3:"data",4:"close",5:"reget"}
            parameter_type = {3:"s"}
            super().__init__( app_id=PP_APPID["Sock5"], 
                            tags_id=tags_id, tags_string=tags_string,
                            parameter_type=parameter_type,**kwargs)
    
    def __init__(self,station,proxy_node,port=7070):
        super().__init__(station,app_id = PP_APPID["Sock5"],callback=None)
        self.proxy_node = proxy_node
        self.proxy_port = port
        if proxy_node == self.station.node_id:
            self.sock5 = Sock5Server(send2Peer = self.send2Peer)
            self.is_sock5_server = True
            self.proxy_type = "https"
        else:
            self.sock5 = Sock5Client(proxy_node= self.proxy_node,port=port,send2Peer= self.send2Peer)
            self.is_sock5_server = False
            

    def start(self):
        super().start()
        self.sock5.start()
        if not self.is_sock5_server:
            session = {"id":0,"size":0,"start":0,"end":0}
            self.send_close(self.proxy_node, session)
        
    def process(self,ppmsg,addr):
#         super().process(ppmsg,addr)
        sock5_msg = self.Sock5Message(bindata=ppmsg.get("app_data"))
#         logging.debug(sock5_msg.dict_data)
        command = sock5_msg.get("command")
        data = sock5_msg.get_parameter("data")
        session = sock5_msg.get_session()

        node_id = ppmsg.get("src_id")
        if command == "send":
            session_id = session["id"] if session else 0
            logging.debug("%d: receive from %d:%d  data(%d) %s"%(self.station.node_id,node_id,session_id,len(data),data[:20]))
            self.sock5.process(node_id,session,data)
        if command == "send_ack":
            logging.debug("%d:%d receive data(%d) %s %s"%(self.station.node_id,session["id"],len(data),data[:20],session))
            self.sock5.process(node_id,session,data)
        if command == "close":
            logging.debug("%d:%d close"%(self.station.node_id,session["id"]))
            self.sock5.close(node_id,session)
        if command == "reget":
            logging.debug("%d :receive session_id %d reget start %d"%(self.station.node_id,
                                                                      session["id"],session["start"]))
            self.send(node_id,session)
    
    def send_close(self,peer_id,session):
        dictdata = {"command":"close",
                    "parameters":{"data":b""}}
        app_msg = self.Sock5Message(dictdata=dictdata)
        app_msg.set_session(session)
        self.send_msg(peer_id, app_msg, need_ack=False)
                 
    def send2Peer(self,peer_id,session,data):
        '''
        data = None close
        data = b''  reget 
        '''
        if data == None:
            self.send_close(peer_id,session)
            return
        command = "send_ack" if self.is_sock5_server else "send"
        dictdata = {"command":command,
                "parameters":{"data":data}}
        app_msg = self.Sock5Message(dictdata=dictdata)
        app_msg.set_session(session)
        self.send_msg(peer_id, app_msg, need_ack=False)
        return session["id"]

    def run_command(self, command_string):
        cmd = command_string.split(" ")
        if cmd[0] in ["stat"]:
            if cmd[0] =="stat":
                print("proxy node:%d run on port %d"%(self.proxy_node,self.proxy_port))
            return True
        return False

class PPProxy(PPNetApp):
    '''
    Sock5Client
        listen on port,send to server 
    Sock5Server
        listen client connect,forward to outside 
    config should have proxy_node = 100 
    '''
    class ProxyMessage(PPNetApp.AppMessage):
        def __init__(self,**kwargs):
            tags_id={"proxy_req":1,"proxy_res":2,"connect_req":20,"connect_res":21,
                     "ip":3,"port":4,"peer_ip":5,"peer_port":6,"node_id":11,"session_id":12}
            parameter_type = {3:"str",4:"I",5:"str",6:"I",11:"I",12:"I"}
            super().__init__( app_id=PP_APPID["Proxy"], 
                            tags_id=tags_id,
#                              tags_string=tags_string,
                            parameter_type=parameter_type,**kwargs)

    
    def __init__(self,station,proxy_node,port=7070):
        super().__init__(station,app_id = PP_APPID["Proxy"],callback=None)
        self.proxy_node = proxy_node
        self.proxy_port = port
        self.connect_port = 0
        self.failure_count = 0
        self.proxy = None
        self.nat_type = 0
        self.proxy_nat_type = 0
        self.external_addr = None
            

    def start(self):
        if self.proxy_node == self.station.node_id:
            self.proxy = ProxyServer(port=self.proxy_port)
            self.is_proxy_server = True
        else:
            self.proxy = ProxyClient(port=self.proxy_port)
            self.is_proxy_server = False
                    
        super().start()
        if self.is_proxy_server:
            self.proxy.start()
            self.is_runing  = True             
        else:
            self.is_runing  = False 
            self.failure_count = 0
            start_new_thread(self.waitProxyServer, ())

    def waitProxyServer(self):
        if self.proxy_node in self.station.peers and self.station.peers[self.proxy_node].status:
            if not self.is_runing :
                self.failure_count += 1
                self.get_proxy()
#                 self.setProxy((self.station.peers[self.proxy_node].ip,7070))
        else:
            self.setProxy(None)
        if not self.station.quitting: 
            if self.failure_count<3:
                self.timer = threading.Timer(1, self.waitProxyServer)
                self.timer.start()
            else:
                print("connect to proxy server %d failure!!!"%self.proxy_node)
     
    def quit(self):
        if self.proxy:
            self.proxy.quit()
        super().quit()       
        
    def get_proxy(self):
        dictdata = {"command":"proxy_req",
                    "parameters":{
                                  "ip":self.station.ip,
                                  "port":self.proxy_port}}
        self.connect_port = random.randint(10000, 60000)
#         proxy_socket = prepare_socket(timeout=5,port=port)
#         try:
#             proxy_socket.connect((self.station.peers[self.proxy_node].ip,self.station.peers[self.proxy_node].port))
#         except:
#             pass
        self.send_msg(self.proxy_node, self.ProxyMessage(dictdata = dictdata))
        return         
        
    def proxy_info(self,addr):
        dictdata = {"command":"proxy_res",
                    "parameters":{
                                  "ip":self.station.ip,
                                  "port":self.proxy_port,
                                  "peer_ip":addr[0],
                                  "peer_port":addr[1]}}
        return self.ProxyMessage(dictdata=dictdata)
    
    def try_connect(self,peer_id,self_addr,peer_addr):
        self.station.beater.send_direct_req(peerid=peer_id,self_addr=self_addr,peer_addr=peer_addr)
        return self.station.beater.direct_connect(self_addr = ("0.0.0.0",self.proxy_port), peer_addr = peer_addr)      
    
    def setProxy(self,addr=None):
        if addr:
            result = self.try_connect(peer_id=self.proxy_node,self_addr = (self.station.ip,self.proxy_port),peer_addr=addr)
#             print(sock,self_nat_type,peer_nat_type,self_addr)
            if result[0]:
                logging.info("proxy server online,start to proxy")
                self.proxy.setProxy(addr)
                self.proxy.start()
                self.external_addr = result[3]
                self.is_runing = True
        else:
            if self.is_runing:
                logging.info("proxy server offline,stop proxy")
                self.proxy.quit()
                self.is_runing = False

    def connectProxy(self,session_id):  #addr=(node_id,session)
        if self.proxy_nat_type == NAT_TYPE["Turnable"]:
            self.proxy.connectRemote(self.proxy.proxy_addr)
        elif self.nat_type == NAT_TYPE["Turnable"] and self.external_addr: #send message to peer to connect self
            dictdata = {"command":"connect_req",
                    "parameters":{
                                  "ip":self.external_addr[0],
                                  "port":self.external_addr[1],
                                  "node_id":self.station.node_id,
                                  "session_id":session_id}}
            self.send_msg(self.proxy_node, self.ProxyMessage(dictdata=dictdata))
        else: #turn server
            pass
     
    def sessionInfo(self,node_id,session):
        dictdata = {"command":"connect_res",
                "parameters":{
                              "node_id":node_id,
                              "session_id":session}}
        return self.ProxyMessage(dictdata=dictdata)
        
    def process(self,ppmsg,addr):
        proxy_msg = self.ProxyMessage(bindata=ppmsg.get("app_data"))
        logging.debug("%d: receive from %s:%d   %s"%(self.station.node_id,addr[0],addr[1],proxy_msg.dict_data))
        command = proxy_msg.get("command")
        node_id = ppmsg.get("src_id")
        if command == "proxy_req":
            self.send_msg(node_id, self.proxy_info(addr))
#             print(self.try_connect(self_addr = ("0.0.0.0",self.proxy_port), 
#                                  peer_addr = (proxy_msg.get_parameter("ip"),proxy_msg.get_parameter("port"))))            
        if command == "proxy_res":
            if not self.is_runing:
                self.setProxy((proxy_msg.get_parameter("ip"),proxy_msg.get_parameter("port")))
#                 print(self.try_connect(self_addr = ("0.0.0.0",self.proxy_port), 
#                                  peer_addr = (proxy_msg.get_parameter("ip"),proxy_msg.get_parameter("port"))))
        if command == "connect_req":
            if not self.is_runing:
                sock = self.proxy.connectRemote((proxy_msg.get_parameter("ip"),proxy_msg.get_parameter("port")))
                if sock:
                    sock.sendall(struct.pack("I",proxy_msg.get_parameter("session_id")))
                
        pass
 
    def run_command(self, command_string):
        cmd = command_string.split(" ")
        if cmd[0] in ["stat","proxy"]:
            if cmd[0] =="stat":
                print("proxy node:%d listen on port %d %s (%d) "%(self.proxy_node,self.proxy_port,
                                                    "is runing " if self.is_runing else "not run",
                                                    self.proxy.count))
            if cmd[0] =="proxy" and len(cmd)>=2:
                print("proxy node set to :%d "%(int(cmd[1]))) 
                self.quit()
                self.proxy_node =  int(cmd[1])
                self.start()
                               
            return True
        return False
    

class Test(unittest.TestCase):


    def setUp(self):
        set_debug(logging.DEBUG, "")
        self.stationA = FakeAppNet(node_id=100)
        self.stationB = FakeAppNet(node_id=200)
        self.client = PPSock5(self.stationA,200,port=443)
        self.server = PPSock5(self.stationB,200)
        processes = {100:self.client.process,
                     200:self.server.process}
        self.stationA.set_process(processes)
        self.stationB.set_process(processes)           
        pass


    def tearDown(self):

        pass
    def testSession(self):
        receive_buffer = ""
        send_buffer = ""
        def receive_process(session,data):
            nonlocal receive_buffer
            receive_buffer += data
        def send_process(session,data):
#             nonlocal send_buffer
#             send_buffer += data
            print(session,data)
            
        s = Session(send_process=send_process,
                          receive_process=receive_process)
        for i in range(10):
            session={"id":100,"size":0,"start":i,"end":i+1}
            print(s.send(session,str(i)))
            rsession={"id":100,"size":0,"start":9-i,"end":9-i+1}
            print(s.receive(rsession, str(9-i)))
        session={"id":100,"size":10,"start":10,"end":10}
        print(s.send(session, "end"))
        print(s.receive(session, "end"))
        print(send_buffer,receive_buffer)
        self.assertEqual(receive_buffer, "0123456789", "test session")
        self.assertEqual(s.receive_size, 10, "test session")
        self.assertEqual(s.send_size, 10, "test session")

    def testSock5Message(self):
        session = {"id":100,"size":0,"start":0,"end":0}
#         self.client.send2Peer(200, session, b"")
        dictdata = {"command":"send",
                "parameters":{"data":b""}}
        app_msg = PPSock5.Sock5Message(dictdata=dictdata)
        print(app_msg.dict_data)
        print(session)
        app_msg.set_session(session)
        print(app_msg.dict_data)
        app_msg1 = PPSock5.Sock5Message(bindata = app_msg.dump())
        print(app_msg1.dict_data)
        print(app_msg.dict_data)
        self.assertDictEqual(app_msg1.dict_data, app_msg.dict_data, "test sock5 msg")

    def TtestSock5(self):
        self.client.start()
        self.server.start()
        time.sleep(1)
        input(prompt="anykey to quit")
        self.client.quit()
        self.server.quit()
        pass

    def TtestProxy(self):
        client = ProxyClient(proxy_addr=("127.0.0.1",7070),send2Peer = None, port = 443)
        server = ProxyServer(port=7070,send2Peer= None)
        server.start()
        client.start()
        s=input()
        server.quit()
        client.quit()
        

        

if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']
    unittest.main()